home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / JFC.bin / JMenu.java < prev    next >
Text File  |  1998-06-30  |  39KB  |  1,220 lines

  1. /*
  2.  * @(#)JMenu.java    1.89 98/04/14
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20.  
  21. package com.sun.java.swing;
  22.  
  23. import java.awt.Component;
  24. import java.awt.Container;
  25. import java.awt.Dimension;
  26. import java.awt.Frame;
  27. import java.awt.Graphics;
  28. import java.awt.LayoutManager;
  29. import java.awt.Point;
  30. import java.awt.Polygon;
  31. import java.awt.Rectangle;
  32. import java.awt.event.*;
  33. import java.beans.*;
  34.  
  35. import java.util.*;
  36. import java.io.Serializable;
  37.  
  38. import com.sun.java.swing.event.*;
  39. import com.sun.java.swing.plaf.*;
  40. import com.sun.java.accessibility.*;
  41.  
  42.  
  43. /**
  44.  * An implementation of a menu -- a popup window containing <code>JMenuItem</code>s that
  45.  * is displayed when the user selects an item on the <code>JMenuBar</code>. In addition
  46.  * to JMenuItems, a JMenu can also contain <code>JSeparator</code>s. 
  47.  * <p>
  48.  * In essence, a menu is a button with an associated JPopupMenu.
  49.  * When the "button" is pressed, the JPopupMenu appears. If the
  50.  * "button" is on the JMenuBar, the menu is a top-level window.
  51.  * If the "button" is another menu item, then the JPopupMenu is
  52.  * "pull-right" menu.
  53.  * <p>
  54.  * For the keyboard keys used by this component in the standard Look and
  55.  * Feel (L&F) renditions, see the
  56.  * <a href="doc-files/Key-Index.html#JMenu">JMenu</a> key assignments.
  57.  * <p>
  58.  * Warning: serialized objects of this class will not be compatible with
  59.  * future swing releases.  The current serialization support is appropriate 
  60.  * for short term storage or RMI between Swing1.0 applications.  It will
  61.  * not be possible to load serialized Swing1.0 objects with future releases
  62.  * of Swing.  The JDK1.2 release of Swing will be the compatibility
  63.  * baseline for the serialized form of Swing objects.
  64.  *
  65.  * @version 1.89 04/14/98
  66.  * @author Georges Saab
  67.  * @author David Karlton
  68.  * @author Arnaud Weber
  69.  * @see JMenuItem
  70.  * @see JSeparator
  71.  * @see JMenuBar
  72.  * @see JPopupMenu
  73.  */
  74. public class JMenu extends JMenuItem implements Accessible,MenuElement
  75. {
  76.     /*
  77.      * The popup menu portion of the menu.
  78.      */
  79.     private JPopupMenu popupMenu;
  80.  
  81.     /*
  82.      * The button's model listeners.
  83.      */
  84.     private ChangeListener menuChangeListener = null;
  85.  
  86.     /*
  87.      * Only one MenuEvent is needed per menu instance since the
  88.      * event's only state is the source property.  The source of events
  89.      * generated is always "this".
  90.      */
  91.     private MenuEvent menuEvent = null;
  92.  
  93.     /**
  94.      * Creates a new JMenu with no text.
  95.      */
  96.     public JMenu() {
  97.         this("");
  98.     }
  99.  
  100.     /**
  101.      * Creates a new JMenu with the supplied string as its text
  102.      *
  103.      * @param s  The text for the menu label
  104.      */
  105.     public JMenu(String s) {
  106.         init(s, null);
  107.         updateUI();
  108.     }
  109.  
  110.     /**
  111.      * Creates a new JMenu with the supplied string as its text
  112.      * and specified as a tear-off menu or not.
  113.      *
  114.      * @param s The text for the menu label
  115.      * @param b can the menu be torn off (not yet implemented)
  116.      */
  117.     public JMenu(String s, boolean b) {
  118.         this(s);
  119.     }
  120.  
  121.     /**
  122.      * Sets the L&F object that renders this component.
  123.      *
  124.      * @param ui the new MenuUI
  125.      * @see getUI
  126.      * @beaninfo
  127.      * description: The menu item's UI delegate
  128.      *       bound: true
  129.      *      expert: true
  130.      *      hidden: true
  131.      */
  132.     public void setUI(MenuUI ui) {
  133.         super.setUI(ui);
  134.     }
  135.     
  136.     /**
  137.      * Notification from the UIFactory that the L&F has changed. 
  138.      * Called to replace the UI with the latest version from the 
  139.      * UIFactory.
  140.      *
  141.      * @see JComponent#updateUI
  142.      */
  143.     public void updateUI() {
  144.         setUI((MenuUI)UIManager.getUI(this));
  145.  
  146.         if ( popupMenu != null )
  147.           {
  148.             popupMenu.setUI((PopupMenuUI)UIManager.getUI(popupMenu));
  149.           }
  150.  
  151.     }
  152.  
  153.  
  154.     /**
  155.      * Returns the name of the L&F class that renders this component.
  156.      *
  157.      * @return "MenuUI"
  158.      * @see JComponent#getUIClassID
  159.      * @see UIDefaults#getUI
  160.      */
  161.     public String getUIClassID() {
  162.         return "MenuUI";
  163.     }
  164.  
  165.     //    public void repaint(long tm, int x, int y, int width, int height) {
  166.     //        Thread.currentThread().dumpStack();
  167.     //        super.repaint(tm,x,y,width,height);
  168.     //    }
  169.  
  170.     /**
  171.      * Set the data model for the "menu button" -- the label
  172.      * that the user clicks to open or close the menu.
  173.      *
  174.      * @param m the ButtonModel
  175.      * @see getModel
  176.      * @beaninfo
  177.      * description: The menu's model
  178.      *       bound: true
  179.      *      expert: true
  180.      *      hidden: true
  181.      */
  182.     public void setModel(ButtonModel newModel) {
  183.         ButtonModel oldModel = getModel();
  184.  
  185.         super.setModel(newModel);
  186.  
  187.         if (oldModel != null) {
  188.             oldModel.removeChangeListener(menuChangeListener);
  189.             menuChangeListener = null;
  190.         }
  191.         
  192.         model = newModel;
  193.         
  194.         if (newModel != null) {
  195.             menuChangeListener = createMenuChangeListener();
  196.             newModel.addChangeListener(menuChangeListener);
  197.         }
  198.     }
  199.  
  200.     /**
  201.      * Returns true if the menu is currently selected (popped up).
  202.      *
  203.      * @return true if the menu is open, else false
  204.      */
  205.     public boolean isSelected() {
  206.         return getModel().isSelected();
  207.     }
  208.  
  209.     /**
  210.      * Sets the selection status of the menu.
  211.      *
  212.      * @param b  a boolean value -- true to select the menu and 
  213.      *           open it, false to unselect the menu and close it
  214.      * @beaninfo
  215.      *      description: When the menu is selected, its popup child is shown.
  216.      *           expert: true
  217.      *           hidden: true
  218.      */
  219.     public void setSelected(boolean b) {
  220.         ButtonModel model = getModel();
  221.         boolean oldValue = model.isSelected();
  222.  
  223.         if ((accessibleContext != null) && (oldValue != b)) {
  224.             if (b) {
  225.                  accessibleContext.firePropertyChange(
  226.                          AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  227.                          null, AccessibleState.SELECTED);
  228.             } else {
  229.                  accessibleContext.firePropertyChange(
  230.                          AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  231.                          AccessibleState.SELECTED, null);
  232.             }
  233.         }
  234.         if (b != model.isSelected()) {
  235.             getModel().setSelected(b);
  236.         }
  237.     }
  238.  
  239.     /**
  240.      * Returns true if the menu's popup window is visible.
  241.      *
  242.      * @return true if the menu is visible, else false
  243.      */
  244.     public boolean isPopupMenuVisible() {
  245.         ensurePopupMenuCreated();
  246.         return popupMenu.isVisible();
  247.     }
  248.  
  249.     /**
  250.      * Set the visibility of the Menu's popup portion.  The popup
  251.      * may only be made visible if the menu is itself showing on
  252.      * the screen.
  253.      *
  254.      * @param b  a boolean value -- true to make the menu visible,
  255.      *           false to hide it
  256.      * @beaninfo
  257.      *      description: The popup menu's visibility
  258.      *           expert: true
  259.      *           hidden: true
  260.      */
  261.     public void setPopupMenuVisible(boolean b) {
  262.         boolean isVisible = isPopupMenuVisible();
  263.         if (b != isVisible) {
  264.             ensurePopupMenuCreated();
  265.             // Set location of popupMenu (pulldown or pullright)
  266.             //  Perhaps this should be dictated by L&F
  267.             int x = 0;
  268.             int y = 0;
  269.             if ((b==true) && isShowing()) {
  270.                 Container parent = getParent();
  271.                 Dimension s = getSize();
  272.                 if (parent instanceof JPopupMenu) {
  273.                     // System.out.println("Pullright: " + getText());
  274.                     x = s.width;
  275.                     y = 0;
  276.                 } else {
  277.                     // System.out.println("Pulldown: " + getText());
  278.                     x = 0;
  279.                     y = s.height;
  280.                 }
  281.                 
  282.                 popupMenu.show(this, x, y);
  283.             } else {
  284.                 popupMenu.setVisible(false);
  285.             }
  286.         }
  287.  
  288.     }
  289.  
  290.     private int delay = 0;
  291.  
  292.     /**
  293.      * Returns the suggested delay before the menu's PopupMenu is popped up or down.
  294.      *
  295.      * @return an int -- the number of milliseconds to delay
  296.      */
  297.     public int getDelay() {
  298.         return delay;
  299.     }
  300.     
  301.     /**
  302.      * Sets the suggested delay before the menu's PopupMenu is popped up or down.
  303.      *
  304.      * @param       d the number of milliseconds to delay
  305.      * @exception   IllegalArgumentException if the value of 
  306.      *                       <code>d</code> is less than 0.
  307.      * @beaninfo
  308.      *      description: The delay between menu selection and making the popup menu visible
  309.      *           expert: true
  310.      */
  311.     public void setDelay(int d) {
  312.         if (d < 0)
  313.             throw new IllegalArgumentException("Delay must be a positive integer");
  314.         
  315.         delay = d;
  316.     }
  317.  
  318.     /**
  319.      * The window-closing listener for the popup.
  320.      *
  321.      * @see WinListener
  322.      */
  323.     protected WinListener popupListener;
  324.  
  325.     private void ensurePopupMenuCreated() {
  326.         if (popupMenu == null) {            
  327.             final JMenu thisMenu = this;
  328.             this.popupMenu = new JPopupMenu();
  329.             popupMenu.setInvoker(this);
  330.             popupListener = createWinListener(popupMenu);
  331.             popupMenu.addPopupMenuListener(new PopupMenuListener() {
  332.                 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
  333.                 }
  334.                 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
  335.                 }
  336.                 public void popupMenuCanceled(PopupMenuEvent e) {
  337.                     ((MenuUI)getUI()).menuCanceled(thisMenu);
  338.                 }
  339.             });
  340.         }
  341.     }
  342.  
  343.     /**
  344.      * Set the location of the popup component
  345.      *
  346.      * @param x the x coordinate of the popup's new position
  347.      * @param y the y coordinate of the popup's new position
  348.      */
  349.     public void setMenuLocation(int x, int y) {
  350.         popupMenu.setLocation(x, y);
  351.     }
  352.  
  353.     /**
  354.      * Appends a menuitem to the end of this menu. 
  355.      * Returns the menuitem added.
  356.      *
  357.      * @param menuItem the JMenuitem to be added
  358.      * @return the JMenuItem added
  359.      */
  360.     public JMenuItem add(JMenuItem menuItem) {
  361.         AccessibleContext ac = menuItem.getAccessibleContext();
  362.         ac.setAccessibleParent(this);
  363.         ensurePopupMenuCreated();
  364.         return popupMenu.add(menuItem);
  365.     }
  366.  
  367.     /**
  368.      * Appends a component to the end of this menu.
  369.      * Returns the component added.
  370.      *
  371.      * @param c the Component to add
  372.      * @return the Component added
  373.      */
  374.     public Component add(Component c) {
  375.         AccessibleContext ac = ((JComponent) c).getAccessibleContext();
  376.         ac.setAccessibleParent(this);
  377.         ensurePopupMenuCreated();
  378.         popupMenu.add(c);
  379.         return c;
  380.     }
  381.  
  382.     /**
  383.      * Creates a new menuitem with the specified text and appends
  384.      * it to the end of this menu.
  385.      *  
  386.      * @param s the string for the menuitem to be added
  387.      */
  388.     public void add(String s) {
  389.         add(new JMenuItem(s));
  390.     }
  391.  
  392.     /**
  393.      * Creates a new menuitem attached to the specified 
  394.      * Action object and appends it to the end of this menu.
  395.      *
  396.      * @param a the Action for the menuitem to be added
  397.      * @see Action
  398.      */
  399.     public JMenuItem add(Action a) {
  400.         JMenuItem mi = new JMenuItem((String)a.getValue(Action.NAME),
  401.                                      (Icon)a.getValue(Action.SMALL_ICON));
  402.         mi.setHorizontalTextPosition(JButton.RIGHT);
  403.         mi.setVerticalTextPosition(JButton.CENTER);
  404.         mi.setEnabled(a.isEnabled());   
  405.         mi.addActionListener(a);
  406.         add(mi);
  407.         PropertyChangeListener actionPropertyChangeListener = 
  408.             createActionChangeListener(mi);
  409.         a.addPropertyChangeListener(actionPropertyChangeListener);
  410.         return mi;
  411.     }
  412.  
  413.     protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
  414.         return new ActionChangedListener(b);
  415.     }
  416.  
  417.     private class ActionChangedListener implements PropertyChangeListener {
  418.         JMenuItem menuItem;
  419.         
  420.         ActionChangedListener(JMenuItem mi) {
  421.             super();
  422.             this.menuItem = mi;
  423.         }
  424.         public void propertyChange(PropertyChangeEvent e) {
  425.             String propertyName = e.getPropertyName();
  426.             if (e.getPropertyName().equals(Action.NAME)) {
  427.                 String text = (String) e.getNewValue();
  428.                 menuItem.setText(text);
  429.             } else if (propertyName.equals("enabled")) {
  430.                 Boolean enabledState = (Boolean) e.getNewValue();
  431.                 menuItem.setEnabled(enabledState.booleanValue());
  432.             } else if (e.getPropertyName().equals(Action.SMALL_ICON)) {
  433.                 Icon icon = (Icon) e.getNewValue();
  434.                 menuItem.setIcon(icon);
  435.                 menuItem.invalidate();
  436.                 menuItem.repaint();
  437.             } 
  438.         }
  439.     }
  440.  
  441.     /**
  442.      * Append a new separator to the end of the menu.
  443.      */
  444.     public void addSeparator() {
  445.         ensurePopupMenuCreated();
  446.         popupMenu.addSeparator();
  447.     }
  448.  
  449.     /**
  450.      * Insert a new menuitem with the specified text at a 
  451.      * given position.
  452.      *
  453.      * @param s the text for the menuitem to add
  454.      * @param pos an int giving the position at which to add the 
  455.      *               new menuitem
  456.      */
  457.     public void insert(String s, int pos) {
  458.         if (pos < 0) {
  459.             throw new IllegalArgumentException("index less than zero.");
  460.         }
  461.  
  462.         ensurePopupMenuCreated();
  463.         popupMenu.insert(new JMenuItem(s), pos);
  464.     }
  465.  
  466.     /**
  467.      * Insert the specified JMenuitem at a given position.
  468.      *
  469.      * @param mi the JMenuitem to add
  470.      * @param pos an int giving the position at which to add the 
  471.      *               new JMenuitem
  472.      */
  473.     public JMenuItem insert(JMenuItem mi, int pos) {
  474.         if (pos < 0) {
  475.             throw new IllegalArgumentException("index less than zero.");
  476.         }
  477.         AccessibleContext ac = mi.getAccessibleContext();
  478.         ac.setAccessibleParent(this);
  479.         ensurePopupMenuCreated();
  480.         popupMenu.insert(mi, pos);
  481.         return mi;
  482.     }
  483.  
  484.     /**
  485.      * Insert a new menuitem attached to the specified Action 
  486.      * object at a given position.
  487.      *
  488.      * @param a the Action object for the menuitem to add
  489.      * @param pos an int giving the position at which to add the 
  490.      *               new menuitem
  491.      */
  492.     public JMenuItem insert(Action a, int pos) {
  493.         if (pos < 0) {
  494.             throw new IllegalArgumentException("index less than zero.");
  495.         }
  496.  
  497.         ensurePopupMenuCreated();
  498.         JMenuItem mi = new JMenuItem((String)a.getValue(Action.NAME));
  499.         mi.addActionListener(a);
  500.         popupMenu.insert(mi, pos);
  501.         a.addPropertyChangeListener(new ActionChangedListener(mi));
  502.         return mi;
  503.     }
  504.  
  505.     /**
  506.      * Inserts a separator at the specified position.
  507.      *
  508.      * @param       index an int giving the position at which to 
  509.      *                    insert the menu separator
  510.      * @exception   IllegalArgumentException if the value of 
  511.      *                       <code>index</code> is less than 0.
  512.      */
  513.     public void insertSeparator(int index) {
  514.         if (index < 0) {
  515.             throw new IllegalArgumentException("index less than zero.");
  516.         }
  517.  
  518.         ensurePopupMenuCreated();
  519.         popupMenu.insert(new JSeparator(), index);
  520.     }
  521.  
  522.     /** 
  523.      * Returns the JMenuItem at the specified position.
  524.      * If the specified position contains a separator, this JMenu
  525.      * is returned.  
  526.      *
  527.      * @param pos    an int giving the position
  528.      * @exception   IllegalArgumentException if the value of 
  529.      *                       <code>index</code> is less than 0.
  530.      */
  531.     public JMenuItem getItem(int pos) {
  532.         if (pos < 0) {
  533.             throw new IllegalArgumentException("index less than zero.");
  534.         }
  535.  
  536.         Component c = getMenuComponent(pos);
  537.         if (c instanceof JMenuItem) {
  538.             JMenuItem mi = (JMenuItem) c;
  539.             return mi;
  540.         }
  541.         // PENDING(ges): Should probably do something better here
  542.         return this;
  543.     }
  544.  
  545.     /**
  546.      * Returns the number of items on the menu, including separators.
  547.      * This method is included for AWT compatibility.
  548.      *
  549.      * @return an int equal to the number of items on the menu
  550.      * @see getMenuComponentCount
  551.      */
  552.     public int getItemCount() {
  553.         return getMenuComponentCount();
  554.     }
  555.  
  556.     /**
  557.      * Returns true if the menu can be torn off.
  558.      *
  559.      * @return true if the menu can be torn off, else false
  560.      */
  561.     public boolean isTearOff() {
  562.         throw new Error("boolean isTearOff() {} not yet implemented");
  563.     }
  564.  
  565.     /**
  566.      * Removes the specified menu item from this menu.
  567.      *
  568.      * @param       item the JMenuItem to be removed from the menu
  569.      */
  570.     public void remove(JMenuItem item) {
  571.         popupMenu.remove(item);
  572.     }
  573.  
  574.     /**
  575.      * Removes the menu item at the specified index from this menu.
  576.      *
  577.      * @param       index the position of the item to be removed. 
  578.      * @exception   IllegalArgumentException if the value of 
  579.      *                       <code>index</code> is less than 0.
  580.      */
  581.     public void remove(int pos) {
  582.         if (pos < 0) {
  583.             throw new IllegalArgumentException("index less than zero.");
  584.         }
  585.  
  586.         popupMenu.remove(pos);
  587.     }
  588.  
  589.     /**
  590.      * Remove all menu items from this menu.
  591.      */
  592.     public void removeAll() {
  593.         int nitems = getMenuComponentCount();
  594.         for (int i = 0 ; i < nitems ; i++) {
  595.             remove(0);
  596.         }
  597.     }
  598.  
  599.     /**
  600.      * Returns the number of components on the menu.
  601.      *
  602.      * @return an int -- the number of components on the menu
  603.      */
  604.     public int getMenuComponentCount() {
  605.         int componentCount = 0;
  606.         if (popupMenu != null)
  607.             componentCount = popupMenu.getComponentCount();
  608.         return componentCount;
  609.     }
  610.  
  611.     /**
  612.      * Returns the component at position n
  613.      *
  614.      * @param n the position of the component to be returned
  615.      */
  616.     public Component getMenuComponent(int n) {
  617.         if (popupMenu != null)
  618.             return popupMenu.getComponent(n);
  619.         
  620.         return null;
  621.     }
  622.  
  623.     /**
  624.      * Returns an array of the menu's subcomponents
  625.      *
  626.      * @return an array of Components
  627.      */
  628.     public Component[] getMenuComponents() {
  629.         if (popupMenu != null)
  630.             return popupMenu.getComponents();
  631.         
  632.         return new Component[0];
  633.     }
  634.  
  635.     /**
  636.      * Gets the parameter string representing the state of this menu. 
  637.      * This string is useful for debugging.
  638.      *
  639.      * @return a String containing the menu parameters
  640.      */
  641.     public String paramString() {
  642.         String str = ", JMenu";
  643.         return super.paramString() + str;
  644.     }
  645.  
  646.     /**
  647.      * Returns true if the menu is a 'top-level menu', that is, if it is
  648.      * the direct child of a menubar.
  649.      *
  650.      * @return true if the menu is activated from the menu bar,
  651.      *         false if the menu is activated from a menu item
  652.      *         on another menu
  653.      */
  654.     public boolean isTopLevelMenu() {
  655.         if (getParent() instanceof JMenuBar)
  656.             return true;
  657.         
  658.         return false;
  659.     }
  660.  
  661.     /**
  662.      * Returns true if the specified component exists in the 
  663.      * submenu hierarchy.
  664.      *
  665.      * @param c the Component to be tested
  666.      * @return true if the component exists
  667.      */
  668.     public boolean isMenuComponent(Component c) {
  669.         // Are we in the MenuItem part of the menu
  670.         if (c == this)
  671.             return true;
  672.         // Are we in the PopupMenu?
  673.         if (c instanceof JPopupMenu) {
  674.             JPopupMenu comp = (JPopupMenu) c;
  675.             if (comp == this.getPopupMenu())
  676.                 return true;
  677.         }
  678.         // Are we in a Component on the PopupMenu
  679.         int ncomponents = this.getMenuComponentCount();
  680.         Component[] component = this.getMenuComponents();
  681.         for (int i = 0 ; i < ncomponents ; i++) {
  682.             Component comp = component[i];
  683.             // Are we in the current component?
  684.             if (comp == c)
  685.                 return true;
  686.             // Hmmm, what about Non-menu containers?
  687.  
  688.             // Recursive call for the Menu case
  689.             if (comp instanceof JMenu) {
  690.                 JMenu subMenu = (JMenu) comp;
  691.                 if (subMenu.isMenuComponent(c))
  692.                     return true;
  693.             }
  694.         }
  695.         return false;
  696.     }
  697.  
  698.  
  699.     /*
  700.      * Returns a point in the coordinate space of this menu's popupmenu
  701.      * which corresponds to the point p in the menu's coordinate space.
  702.      *
  703.      * @param p the point to be translated
  704.      */
  705.     private Point translateToPopupMenu(Point p) {
  706.         return translateToPopupMenu(p.x, p.y);
  707.     }
  708.  
  709.     /*
  710.      * Returns a point in the coordinate space of this menu's popupmenu
  711.      * which corresponds to the point (x,y) in the menu's coordinate space.
  712.      * @param x the x coordinate of the point to be translated
  713.      * @param y the y coordinate of the point to be translated
  714.      */
  715.     private Point translateToPopupMenu(int x, int y) {
  716.             int newX;
  717.             int newY;
  718.  
  719.             if (getParent() instanceof JPopupMenu) {
  720.                 newX = x - getSize().width;
  721.                 newY = y;
  722.             } else {
  723.                 newX = x;
  724.                 newY = y - getSize().height;
  725.             }
  726.  
  727.             return new Point(newX, newY);
  728.         }
  729.  
  730.     /**
  731.      * Returns the popupmenu associated with this menu
  732.      */
  733.     public JPopupMenu getPopupMenu() {
  734.         ensurePopupMenuCreated();
  735.         return popupMenu;
  736.     }
  737.  
  738.     /**
  739.      * Add a listener for menu events
  740.      *
  741.      * @param l the listener to be added
  742.      */
  743.     public void addMenuListener(MenuListener l) {
  744.         listenerList.add(MenuListener.class, l);
  745.     }
  746.     
  747.     /**
  748.      * Remove a listener for menu events
  749.      *
  750.      * @param l the listener to be removed
  751.      */
  752.     public void removeMenuListener(MenuListener l) {
  753.         listenerList.remove(MenuListener.class, l);
  754.     }
  755.  
  756.     /**
  757.      * Notify all listeners that have registered interest for
  758.      * notification on this event type.  The event instance 
  759.      * is lazily created using the parameters passed into 
  760.      * the fire method.
  761.      *
  762.      * @see EventListenerList
  763.      */
  764.     protected void fireMenuSelected() {
  765.         // Guaranteed to return a non-null array
  766.         Object[] listeners = listenerList.getListenerList();
  767.         // Process the listeners last to first, notifying
  768.         // those that are interested in this event
  769.         for (int i = listeners.length-2; i>=0; i-=2) {
  770.             if (listeners[i]==MenuListener.class) {
  771.                 if (listeners[i+1]== null) {
  772.                     throw new Error(getText() +" has a NULL Listener!! " 
  773.                                        + i);
  774.                 } else {
  775.                     // Lazily create the event:
  776.                     if (menuEvent == null)
  777.                         menuEvent = new MenuEvent(this);
  778.                     ((MenuListener)listeners[i+1]).menuSelected(menuEvent);
  779.                 }              
  780.             }
  781.         }
  782.     }
  783.  
  784.     /**
  785.      * Notify all listeners that have registered interest for
  786.      * notification on this event type.  The event instance 
  787.      * is lazily created using the parameters passed into 
  788.      * the fire method.
  789.      *
  790.      * @see EventListenerList
  791.      */
  792.     protected void fireMenuDeselected() {
  793.         // Guaranteed to return a non-null array
  794.         Object[] listeners = listenerList.getListenerList();
  795.         // Process the listeners last to first, notifying
  796.         // those that are interested in this event
  797.         for (int i = listeners.length-2; i>=0; i-=2) {
  798.             if (listeners[i]==MenuListener.class) {
  799.                 if (listeners[i+1]== null) {
  800.                     System.out.println(getText() +" has a NULL Listener!! " 
  801.                                        + i);
  802.                 } else {
  803.                     // Lazily create the event:
  804.                     if (menuEvent == null)
  805.                         menuEvent = new MenuEvent(this);
  806.                     ((MenuListener)listeners[i+1]).menuDeselected(menuEvent);
  807.                 }              
  808.             }
  809.         }
  810.     }
  811.  
  812.     class MenuChangeListener implements ChangeListener, Serializable {
  813.         boolean isSelected = false;
  814.         public void stateChanged(ChangeEvent e) {               
  815.             DefaultButtonModel model = (DefaultButtonModel) e.getSource();
  816.             boolean modelSelected = model.isSelected();
  817.             // System.out.println("Model selected = " + modelSelected +
  818.             // " isSelected = " + isSelected);
  819.             if (modelSelected != isSelected) {
  820.                 if (modelSelected == true) {
  821.                     fireMenuSelected();
  822.                 } else {
  823.                     fireMenuDeselected();
  824.                 }
  825.                 isSelected = modelSelected;
  826.             }
  827.         }
  828.     }
  829.  
  830.     private ChangeListener createMenuChangeListener() {
  831.         return new MenuChangeListener();
  832.     }
  833.  
  834.     /**
  835.      * Create a window-closing listener for the popup.
  836.      *
  837.      * @param p the JPopupMenu
  838.      * @see WinListener
  839.      */
  840.     protected WinListener createWinListener(JPopupMenu p) {
  841.         return new WinListener(p);
  842.     }
  843.  
  844.     /**
  845.      * A listener class that watches for a popup window closing.
  846.      * When the popup is closing, the listener deselects the menu.
  847.      * <p>
  848.      * Warning: serialized objects of this class will not be compatible with
  849.      * future swing releases.  The current serialization support is appropriate
  850.      * for short term storage or RMI between Swing1.0 applications.  It will
  851.      * not be possible to load serialized Swing1.0 objects with future releases
  852.      * of Swing.  The JDK1.2 release of Swing will be the compatibility
  853.      * baseline for the serialized form of Swing objects.
  854.      */
  855.     protected class WinListener extends WindowAdapter implements Serializable {
  856.         JPopupMenu popupMenu;
  857.         /**
  858.          *  Create the window listener for the specified popup.
  859.          */
  860.         public WinListener(JPopupMenu p) {
  861.             this.popupMenu = p;
  862.         }
  863.         /**
  864.          * Deselect the menu when the popup is closed from outside.
  865.          */
  866.         public void windowClosing(WindowEvent e) {
  867.             setSelected(false);
  868.         }
  869.     }
  870.  
  871.     /**
  872.      * Process a mouse event. event is a MouseEvent with source being the receiving component.
  873.      * componentPath is the path of the receiving MenuComponent in the menu
  874.      * hierarchy. manager is the MenuSelectionManager for the menu hierarchy.
  875.      * This method should process the MouseEvent and change the menu selection if necessary
  876.      * by using MenuSelectionManager's API
  877.      * Note: you do not have to forward the event to sub-components. This is done automatically
  878.      * by the MenuSelectionManager
  879.      */
  880.     public void processMouseEvent(MouseEvent event,MenuElement path[],MenuSelectionManager manager) {
  881.         ((MenuUI)getUI()).processMouseEvent(this,event,path,manager);
  882.     }
  883.  
  884.     /** Implemented to be a MenuElement. This message is forwarded to the UI **/
  885.     public void processKeyEvent(KeyEvent e,MenuElement path[],MenuSelectionManager manager) {
  886.         ((MenuUI)getUI()).processKeyEvent(this,e,path,manager);
  887.     }
  888.  
  889.     /**
  890.      *
  891.      */
  892.     public void menuSelectionChanged(boolean isIncluded) {
  893.         setSelected(isIncluded);
  894.     }
  895.  
  896.     /**
  897.      * This method should return an array containing the sub-menu components for this menu component
  898.      */
  899.     public MenuElement[] getSubElements() {
  900.         if(popupMenu == null)
  901.             return new MenuElement[0];
  902.         else {
  903.             MenuElement result[] = new MenuElement[1];
  904.             result[0] = popupMenu;
  905.             return result;
  906.         }
  907.     }
  908.  
  909.     
  910.     /*
  911.      * This method should return the java.awt.Component used to paint this MenuElement.
  912.      * The returned component will be used to convert events and detect if an event is inside
  913.      * a menu component.
  914.      */
  915.     public Component getComponent() {
  916.         return this;
  917.     }
  918.  
  919.  
  920.     /* 
  921.      * setAccelerator() is not defined for JMenu.  Use setMnemonic() instead. 
  922.      *
  923.      * @beaninfo
  924.      *     description: The keystroke combination which will invoke the JMenuItem's
  925.      *                  actionlisteners without navigating the menu hierarchy
  926.      *          hidden: true
  927.      */
  928.     public void setAccelerator(KeyStroke keyStroke) {
  929.         throw new Error("setAccelerator() is not defined for JMenu.  Use setMnemonic() instead.");
  930.     }
  931.  
  932.     /**
  933.      *
  934.      */
  935.     protected void processKeyEvent(KeyEvent e) {
  936.         MenuSelectionManager.defaultManager().processKeyEvent(e);
  937.         if(e.isConsumed())
  938.             return;
  939.         super.processKeyEvent(e);
  940.     }
  941. /////////////////
  942. // Accessibility support
  943. ////////////////
  944.  
  945.     /**
  946.      * Get the AccessibleContext associated with this JComponent
  947.      *
  948.      * @return the AccessibleContext of this JComponent
  949.      */
  950.     public AccessibleContext getAccessibleContext() {
  951.         if (accessibleContext == null) {
  952.             accessibleContext = new AccessibleJMenu();
  953.         }
  954.         return accessibleContext;
  955.     }
  956.  
  957.     /**
  958.      * The class used to obtain the accessible role for this object.
  959.      * <p>
  960.      * Warning: serialized objects of this class will not be compatible with
  961.      * future swing releases.  The current serialization support is appropriate
  962.      * for short term storage or RMI between Swing1.0 applications.  It will
  963.      * not be possible to load serialized Swing1.0 objects with future releases
  964.      * of Swing.  The JDK1.2 release of Swing will be the compatibility
  965.      * baseline for the serialized form of Swing objects.
  966.      */
  967.     protected class AccessibleJMenu extends AccessibleJMenuItem 
  968.     implements AccessibleSelection {
  969.  
  970.         /**
  971.          * Returns the number of accessible children in the object.  If all
  972.          * of the children of this object implement Accessible, than this
  973.          * method should return the number of children of this object.
  974.          *
  975.          * @return the number of accessible children in the object.
  976.          */
  977.         public int getAccessibleChildrenCount() {
  978.             Component[] children = getMenuComponents();
  979.             int count = 0;
  980.             for (int j = 0; j < children.length; j++) {
  981.                 if (children[j] instanceof Accessible) {
  982.                     count++;
  983.                 }
  984.             }
  985.             return count;
  986.         }
  987.  
  988.         /**
  989.          * Return the nth Accessible child of the object.  
  990.          *
  991.          * @param i zero-based index of child
  992.          * @return the nth Accessible child of the object
  993.          */
  994.         public Accessible getAccessibleChild(int i) {
  995.             Component[] children = getMenuComponents();
  996.             int count = 0;
  997.             for (int j = 0; j < children.length; j++) {
  998.                 if (children[j] instanceof Accessible) {
  999.                     if (count == i) {
  1000.                         if (children[j] instanceof JComponent) {
  1001.                             // FIXME:  [[[WDW - probably should set this when
  1002.                             // the component is added to the menu.  I tried
  1003.                             // to do this in most cases, but the separators
  1004.                             // added by addSeparator are hard to get to.]]]
  1005.                             AccessibleContext ac = ((Accessible) children[j]).getAccessibleContext();
  1006.                             ac.setAccessibleParent(JMenu.this);
  1007.                         }
  1008.                         return (Accessible) children[j];
  1009.                     } else {
  1010.                         count++;
  1011.                     }
  1012.                 }
  1013.             }
  1014.             return null;
  1015.         }
  1016.  
  1017.         /**
  1018.          * Get the role of this object.
  1019.          *
  1020.          * @return an instance of AccessibleRole describing the role of the 
  1021.          * object
  1022.          * @see AccessibleRole
  1023.          */
  1024.         public AccessibleRole getAccessibleRole() {
  1025.             return AccessibleRole.MENU;
  1026.         }
  1027.  
  1028.         /**
  1029.          * Get the AccessibleSelection associated with this object if one
  1030.          * exists.  Otherwise return null.
  1031.          */
  1032.         public AccessibleSelection getAccessibleSelection() {
  1033.             return this;
  1034.         }
  1035.  
  1036.         /**
  1037.          * Returns 1 if a sub-menu is currently selected in this menu.
  1038.          *
  1039.          * @return 1 if a menu is currently selected, else 0
  1040.          */
  1041.         public int getAccessibleSelectionCount() {
  1042.         MenuElement me[] =
  1043.         MenuSelectionManager.defaultManager().getSelectedPath();
  1044.         if (me != null) {
  1045.         for (int i = 0; i < me.length; i++) {
  1046.             if (me[i] == JMenu.this) {   // this menu is selected
  1047.             if (i+1 < me.length) {
  1048.                 return 1;
  1049.             }
  1050.             }
  1051.         }
  1052.         }
  1053.         return 0;
  1054.         }
  1055.  
  1056.         /**
  1057.          * Returns the currently selected sub-menu if one is selected,
  1058.          * otherwise null (there can only be one selection, and it can
  1059.      * only be a sub-menu, as otherwise menu items don't remain
  1060.      * selected).
  1061.          */
  1062.         public Accessible getAccessibleSelection(int i) {
  1063.         // if i is a sub-menu & popped, return it
  1064.         if (i < 0 || i >= getItemCount()) {
  1065.         return null;
  1066.         }
  1067.         MenuElement me[] = 
  1068.         MenuSelectionManager.defaultManager().getSelectedPath();
  1069.             if (me != null) {
  1070.         for (int j = 0; j < me.length; j++) {
  1071.             if (me[j] == JMenu.this) {   // this menu is selected
  1072.             // so find the next JMenuItem in the MenuElement 
  1073.             // array, and return it!
  1074.             while (++j < me.length) {
  1075.                 if (me[j] instanceof JMenuItem) {
  1076.                 return (Accessible) me[j];
  1077.                 }
  1078.             }
  1079.             }
  1080.         }
  1081.         }
  1082.         return null;
  1083.         }
  1084.  
  1085.         /**
  1086.          * Returns true if the current child of this object is selected.
  1087.      * (i.e. if this child is a pop-ed up sub-menu)
  1088.          *
  1089.          * @param i the zero-based index of the child in this Accessible
  1090.          * object.
  1091.          * @see AccessibleContext#getAccessibleChild
  1092.          */
  1093.         public boolean isAccessibleChildSelected(int i) {
  1094.         // if i is a sub-menu and is pop-ed up, return true, else false
  1095.         MenuElement me[] = 
  1096.         MenuSelectionManager.defaultManager().getSelectedPath();
  1097.         if (me != null) {
  1098.         JMenuItem mi = JMenu.this.getItem(i);
  1099.         for (int j = 0; j <= me.length; j++) {
  1100.             if (me[j] == mi) {
  1101.             return true;
  1102.             }
  1103.         }
  1104.         }
  1105.         return false;
  1106.         }
  1107.  
  1108.     /*
  1109.      * Build an array of menu elements - from my PopupMenu to the root
  1110.      * JMenuBar
  1111.      */
  1112.     private MenuElement[] buildMenuElementArray(JMenu leaf) {
  1113.         Vector elements = new Vector();
  1114.         Component current = leaf.getPopupMenu();
  1115.         JPopupMenu pop;
  1116.         JMenu menu;
  1117.         JMenuBar bar;
  1118.  
  1119.         while (true) {
  1120.         if (current instanceof JPopupMenu) {
  1121.             pop = (JPopupMenu) current;
  1122.             elements.insertElementAt(pop, 0);
  1123.             current = pop.getInvoker();
  1124.         } else if (current instanceof JMenu) {
  1125.             menu = (JMenu) current;
  1126.             elements.insertElementAt(menu, 0);
  1127.             current = menu.getParent();
  1128.         } else if (current instanceof JMenuBar) {
  1129.             bar = (JMenuBar) current;
  1130.             elements.insertElementAt(bar, 0);
  1131.             MenuElement me[] = new MenuElement[elements.size()];
  1132.             elements.copyInto(me);
  1133.             return me;
  1134.         }
  1135.         }
  1136.     }
  1137.  
  1138.         /**
  1139.          * Selects the nth menu in the menu.  If that item is a sub-menu,
  1140.          * it will pop up in response.  If a different item is already
  1141.      * popped up, this will force it to close.  If this is a sub-menu
  1142.      * that is already poppoed up (selected), this method has no
  1143.      * effect.
  1144.          *
  1145.          * @param i the zero-based index of selectable items
  1146.          * @see #getAccessibleStateSet
  1147.          */
  1148.         public void addAccessibleSelection(int i) {
  1149.         if (i < 0 || i >= getItemCount()) {
  1150.         return;
  1151.         }
  1152.         JMenuItem mi = getItem(i);
  1153.         if (mi != null) {
  1154.         if (mi instanceof JMenu) {
  1155.             MenuElement me[] = buildMenuElementArray((JMenu) mi);
  1156.             MenuSelectionManager.defaultManager().setSelectedPath(me);
  1157.         } else {
  1158.             mi.doClick();
  1159.             MenuSelectionManager.defaultManager().setSelectedPath(null);
  1160.             }
  1161.         }
  1162.         }
  1163.  
  1164.         /**
  1165.          * Removes the nth item from the selection.  In general, menus 
  1166.      * can only have one item within them selected at a time 
  1167.      * (e.g. one sub-menu popped open).
  1168.          *
  1169.          * @param i the zero-based index of the selected item
  1170.          */
  1171.         public void removeAccessibleSelection(int i) {
  1172.         if (i < 0 || i >= getItemCount()) {
  1173.         return;
  1174.         }
  1175.         JMenuItem mi = getItem(i);
  1176.         if (mi != null && mi instanceof JMenu) {
  1177.         if (((JMenu) mi).isSelected()) {
  1178.             MenuElement old[] =
  1179.             MenuSelectionManager.defaultManager().getSelectedPath();
  1180.             MenuElement me[] = new MenuElement[old.length-1];
  1181.             for (int j = 0; j < old.length -1; j++) {
  1182.             me[j] = old[j];
  1183.             }
  1184.             MenuSelectionManager.defaultManager().setSelectedPath(me);
  1185.         }
  1186.             }
  1187.         }
  1188.  
  1189.         /**
  1190.          * Clears the selection in the object, so that nothing in the
  1191.          * object is selected.  This will close any open sub-menu.
  1192.          */
  1193.         public void clearAccessibleSelection() {
  1194.         // if this menu is selected, reset selection to only go
  1195.         // to this menu; else do nothing
  1196.         MenuElement old[] = 
  1197.         MenuSelectionManager.defaultManager().getSelectedPath();
  1198.         if (old != null) {
  1199.         for (int j = 0; j < old.length; j++) {
  1200.             if (old[j] == JMenu.this) {  // menu is in the selection!
  1201.             MenuElement me[] = new MenuElement[j+1];
  1202.             System.arraycopy(old, 0, me, 0, j);
  1203.             me[j] = JMenu.this.getPopupMenu();
  1204.             MenuSelectionManager.defaultManager().setSelectedPath(me);
  1205.             }
  1206.         }
  1207.             }
  1208.         }
  1209.  
  1210.         /**
  1211.          * Normally causes every selected item in the object to be selected
  1212.          * if the object supports multiple selections.  This method
  1213.          * makes no sense in a menu bar, and so does nothing.
  1214.          */
  1215.         public void selectAllAccessibleSelection() {
  1216.         }
  1217.     } // inner class AccessibleJMenu
  1218. }
  1219.  
  1220.